/**
* @author Wanderley Caloni Jr <wanderley@caloni.com.br>
* @date 18.set.2005
*/
#include <winerror.h>
#include <atlbase.h>
#include <atlexcept.h>
#include <string>

#pragma warning( push )
#pragma warning( disable : 4290 ) // throw specification (see Assign() func)


/**
* Helper template to throw or not throw exception based 
* on static choise (true or false).
*
* @author CoffeeBuzz (idea from Code Project comment).
* @date 2008-05
*/
template<bool ThrowException>
struct HResultChecker
{
    static void Check(HRESULT hr);
};

template<> struct HResultChecker<false>
{
    static void Check(HRESULT hr) 
    {
        hr;
    }
};

template<> struct HResultChecker<true>
{
    static void Check(HRESULT hr)
    { 
        if( FAILED(hr) ) 
            AtlThrow(hr);
    }
};


/**
* Use this class instead HRESULT in order to the assignement operator be 
* tested. In case of failure, the funcion AtlThrow() will be called.
*
* @sa AtlThrow(), CAtlException.
*/
template<bool ThrowException>
class HResultT
{
public:
    /// Test the HRESULT in the constructor.
    HResultT(HRESULT hr = S_OK)	{ Assign(hr); }

    /// Test failure of the received hr. If FAILED(hr), the function 
    /// AtlThrow() will be called.
    HResultT &operator = (HRESULT hr)
    {
        Assign(hr);
        return *this;
    }

    /** 
    * Retrieves the error desription of the HRESULT member.
    * @return string message for the HRESULT.
    *
    * @author ddarko (comment from CodeProject)
    * @date 2005-09
    */
    LPCTSTR ErrorMessage()
    {
        //Borrowed from <comdef.h>
        if (m_desc.empty()) //Create new error message
        {
            LPTSTR pszMsg = NULL;

            /// @todo support locale
            FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | 
                FORMAT_MESSAGE_FROM_SYSTEM,
                NULL,
                m_hr,
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                (LPTSTR)&pszMsg,
                0,
                NULL);

            if (pszMsg != NULL)  //Annex CRLF to the message
            {
                int nLen = lstrlen(pszMsg);
                if (nLen > 1 && pszMsg[nLen - 1] == '\n') 
                {
                    pszMsg[nLen - 1] = 0;
                    if (pszMsg[nLen - 2] == '\r') 
                    {
                        pszMsg[nLen - 2] = 0;
                    }
                }
            } 
            else //if HRESULT is without a description, provide one
            {
                const int ERRMSG_MAXSIZE = 32;
                pszMsg = (LPTSTR)LocalAlloc(0, ERRMSG_MAXSIZE * sizeof(TCHAR));
                if (pszMsg != NULL) 
                {
                    //Check errors for Custom Interfaces Facility of FACILITY_ITF (see winerror.h)
                    //Error codes for this facility could have duplicate descriptions (see MSDN Q183216)
                    const unsigned short E_ITF_FIRST = 0x200; //first error in the range
                    const HRESULT WCODE_HRESULT_FIRST = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, E_ITF_FIRST);
                    const HRESULT WCODE_HRESULT_LAST  = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF+1, 0);
                    if (m_hr >= WCODE_HRESULT_FIRST &&  m_hr < WCODE_HRESULT_LAST) 
                        wsprintf(pszMsg, TEXT("COM Interface error 0x%0lX"), m_hr);
                    else
                        wsprintf(pszMsg, TEXT("Unknown COM error 0x%0lX"), m_hr);
                }
            }

            if( pszMsg )
            {
                m_desc = pszMsg;
                LocalFree(pszMsg);
            }
        }

        return m_desc.c_str();
    }

    /// Extractor of the stored HRESULT.
    operator HRESULT () { return m_hr; }

private:
    void Assign(HRESULT hr) // throw( CAtlException )
    {
        HResultChecker<ThrowException>::Check(m_hr = hr);
    }

    HRESULT m_hr; // the stored HRESULT
    std::basic_string<TCHAR> m_desc; // error description
};

/// Throw exception version.
typedef HResultT<true> HResult;

// No-Throw exception version.
typedef HResultT<false> HResultSafe;

#pragma warning( pop )
